#include "Col.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "Stream.h"
#include "StreamBuf.h"
#include "FileBuf.h"

#include "Class.h"
#include "String.h"
#include "CType.h"
#include "ObjArray.h"
#include "OrdColl.h"
#include "Error.h"
#include "HexFilter.h"
#include "StringFilter.h"

OStream gCout(1);               // UNIX output Stream 1
OStream gCerr(2, 0, 0);         // UNIX output Stream 2
IStream gCin((int)0, &gCout);   // UNIX input Stream 0 tied to cout

extern "C" double atof(const char*);

//---- Stream ------------------------------------------------------------------

Stream::Stream(StreamBuf *s, bool dodelete)
{
	bp= s;
	delete_StreamBuf= dodelete;
	state= _good;
	deepclone= FALSE;
	it= 0;
}

Stream::~Stream()
{
	if (it) {
		if (all) {
			all->RemovePtr(it);
			if (all->Size() <= 0) {
				OrdCollection *tmp= all;
				all= 0;
				delete tmp;
			}
		}
		SafeDelete(it);
	}
	if (bp) {
		bp->Finish();
		if (delete_StreamBuf)
			delete bp;
		bp= 0;
	}
}

void Stream::Push(StreamBuf *s)
{
	if (s) {
		s->SetBaseStream(bp);
		bp= s;
	}
}

void Stream::Pop()
{
	if (bp) {
		StreamBuf *oldsb= bp;
		oldsb->Finish();
		bp= oldsb->GetBaseStream();
		state= _good;
		delete oldsb;
	}
}

long Stream::tell()
{
	return bp->tell();
}

//---- Misc --------------------------------------------------------------------

OrdCollection *Stream::all;

ObjArray *Stream::Table()
{
	if (it == 0) {
		it= new ObjArray(10);
		if (all == 0)
			all= new OrdCollection;
		all->Add(it);
	}
	return it;
}

int Stream::IndexOfPtr(Object *op)
{
	return Table()->IndexOfPtr(op);
}

Object *Stream::At(int ix)
{
	return Table()->At(ix);
}

void Stream::AtPut(int ix, Object *op)
{
	if (op)
		Table()->AtPutAndExpand(ix, op);
}

int Stream::MakeIndex(Object *op)
{
	return Table()->Index(op);
}

void Stream::InvalidatePtr(Object *op)
{
	if (all && op) {
		Iter next(all);
		register ObjArray *oa;
		int ix;
		
		while (oa= (ObjArray*) next())
			if (oa != op)
				if ((ix= oa->IndexOfPtr(op)) >= 0)
					oa->AtPut(ix, (Object*) 0x01);
	}
}

//---- OStream -----------------------------------------------------------------

OStream::OStream(StreamBuf *s, bool dodelete) : Stream(s, dodelete)
{
}

OStream::OStream(int fd) : Stream(new Filebuf(fd), TRUE)
{
}

OStream::OStream(int fd, char *b, int sz) : Stream(new Filebuf(fd, b, sz), TRUE)
{
}

OStream::OStream(int size, char *p) : Stream(new StreamBuf(size, p), TRUE)
{
}

OStream::OStream(const char *path) : Stream(new Filebuf(path, output), TRUE)
{
	if (! ((Filebuf*)bp)->isopen())
		state |= _fail;
}

OStream::~OStream()
{
	flush();
}

OStream& OStream::seek(long pos, bool relative)
{
	if (pos == 0 && relative)
		return *this;
	bp->seek(pos, relative);
	return *this;
}

long OStream::tell()
{
	return bp->tell();
}

OStream& OStream::flush()
{
	if (bp)
		bp->overflow();
	return *this;
}

OStream& OStream::put(char c)
{
	if (state == 0)
		if (bp->sputc(c) == EOF)
			state |= _eof | _fail;
	return *this;
}

OStream& OStream::put(u_char b)
{
	if (state == 0) {
		int c= b;
		if (bp->sputc(c) == EOF)
			state |= _eof | _fail;
	}
	return *this;
}

OStream& OStream::form(char *va_(fmt), ...)
{
	if (state == 0) {
		va_list ap;
		va_start(ap,va_(fmt));
		char *format= va_(fmt);
		char buf[BUFSIZE];

		register int ll= (int) vsprintf(buf, format, ap);
		if (0 < ll && ll < BUFSIZE)                 // length
			;
		else if (buf < (char*)ll && (char*)ll < buf+BUFSIZE) // pointer to trailing 0
			ll= (char*)ll - buf;
		else
			ll= strlen(buf);
		write(buf, ll);
		va_end(ap);
	}
	return *this;
}

OStream &OStream::Print(int code, void *vp)
{
	register StreamBuf *nbp= bp;

	if (state)
		return *this;
		
	switch (code) {
	case eTNone:
		break;
	case eTChar:
	case eTUChar:
		if (nbp->sputc((int)vp) == EOF)
			state |= _fail;
		break;

	case eTShort:
		form("%d", (long)vp);
		break;
	case eTUShort:
		form("%u", (u_long)vp);
		break;
	case eTInt:
		form("%d", (long)vp);
		break;
	case eTUInt:
		form("%u", (u_long)vp);
		break;
	case eTLong:
		form("%ld", (long)vp);
		break;
	case eTULong:
		form("%lu", (u_long)vp);
		break;

	case eTFloat:
		form("%lg", (double)*((float*)vp));
		break;

	case eTDouble:
		form("%lg", *((double*)vp));
		break;

	case eTString:
	case eTUString:
		if (vp && *((char*)vp))
			write((const char*)vp, strlen((char*)vp));
		break;

	case eTStream:
		{
			register StreamBuf *b= ((Stream*)vp)->getsbuf();
			register int c= b->sgetc();
			while (c != EOF) {
				nbp->sputc(c);
				//if (nbp->sputc(c) == EOF) {
					//fprintf(stderr, "oops: %d\n", c);
					//state|= _fail;
					//break;
				//}
				c= b->snextc();
			}
		}
		break;
	case eTVoid:
		form("0x%08x", vp);
		fprintf(stderr, "OStream::Print: void*, be careful\n");
		break;
	default:
		fprintf(stderr, "OStream::Print: unknown code %d\n", code);
		break;
	}
	return *this;
}

int OStream::write(const u_char *s, int n)
{
	if (state || n <= 0)
		return 0;
	int r= bp->sputn((char*)s, n);
	if (r != n)
		setstate((state_value)(_eof|_fail));
	return r;
}

OStream &OStream::PrintString(const char *s, int l)
{
	if (s == 0) {       // a null ptr is written as -1
		(*this) << -1;
	} else {
		if (l < 0)
			l= strlen(s)+1;
		(*this) << l;
		if (0) {
			put('[');
			write(s, l);
		} else {
			put('\"');
			Push(new StringEncoder);
			if (s[l-1])
				write(s, l);
			else
				write(s, l-1);
			Pop();
		}
	}
	put(' ');
	return (*this);
}

OStream &OStream::PrintHexString(const char *s, int l)
{
	if (l < 0)
		l= strlen(s)+1;
	(*this) << l;
	put(' ');
	put('<');
	Push(new HexEncoder(">"));
	write(s, l);
	Pop();
	put(' ');
	return (*this);
}

void OStream::PutBigEndian(int bytes, long v)
{
	register StreamBuf *nbp= bp;
	while (bytes-- > 0)
		nbp->sputc((int)((v >> bytes*8) & 0xff));
}

//---- IStream -----------------------------------------------------------------

IStream::IStream(StreamBuf *s, OStream *t, bool dodelete) : Stream(s, dodelete)        // bind to buffer
{
	tied_to= t;
}

IStream::IStream(const char *p, int l) : Stream(new StreamBuf(l, p, l), TRUE)    // bind to string
{
	tied_to= 0;
}

IStream::IStream(int fd, OStream *t) : Stream(new Filebuf(fd), TRUE) // bind to file
{
	tied_to= t;
}

IStream::IStream(const char *path) : Stream(new Filebuf(path, input), TRUE)        // bind to file
{
	tied_to= 0;
	if (! ((Filebuf*)bp)->isopen())
		state |= _fail;
}

IStream& IStream::seek(long pos, bool relative)
{
	if (pos != 0 || !relative)
		bp->seek(pos, relative);
	return *this;
}

long IStream::tell()
{
	return bp->tell();
}

OStream *IStream::tie(OStream *s)
{
	OStream *t= tied_to;
	tied_to= s;
	return t;
}

void IStream::eatwhite()
{
	flush();
	register StreamBuf *nbp= bp;
	register int c= nbp->sgetc();

	while (Isspace(zapeof(c)))
		c= nbp->snextc();
	if (c == EOF)
		state |= _eof;
}

int IStream::read(u_char *s, int n)
{
	if (state || n <= 0) {
		state |= _fail;
		return 0;
	}
	int r= bp->sgetn((char*)s, n);
	if (r < n) {
		state= _fail | _eof;
	}
	return r;
}

IStream& IStream::get(char &c)  // single character
{
	flush();

	if (state) {
		state |= _fail;
		return *this;
	}
	register tc= bp->sgetc();
	if (tc == EOF) {
		state |= _fail|_eof;
	} else {
		c= tc;
		bp->stossc();
	}
	return *this;
}

IStream& IStream::peek(char &c)  // single character
{
	flush();

	if (state) {
		state |= _fail;
		return *this;
	}
	register int tc= bp->sgetc();
	if (tc == EOF) {
		state |= _fail|_eof;
	} else {
		c= tc;
	}
	return *this;
}

IStream& IStream::peek(u_char &b)
{
	flush();

	if (state) {
		state |= _fail;
		return *this;
	}
	register int tc= bp->sgetc();
	if (tc == EOF)
		state |= _fail|_eof;
	else {
		b= (u_char) tc;
	}
	return *this;
}

IStream& IStream::Scan(int code, void *vp)
{
	register StreamBuf *nbp= bp;
	register int c;
	register long l= 0;
	char buf[256];
	register char *s;
	int neg= 0;

	eatwhite();

	if (state) {
		state |= _fail;
		return *this;
	}

	c= nbp->sgetc();
	if (c == EOF) {
		state |= _fail;
		return *this;
	}

	switch (code) {
	case eTNone:
		break;
	case eTChar:
	case eTUChar:
		if (code == eTChar)
			*((char*)vp)= c;
		else
			*((u_char*)vp)= c;
		nbp->stossc();
		break;
	case eTShort:
	case eTUShort:
	case eTInt:
	case eTUInt:
	case eTLong:
	case eTULong:
		switch (c) {
		case '-':
		case '+':
			neg= c;
			c= nbp->snextc();
			break;
		}
		
		if (Isdigit(c)) {
			do {
				l= l*10+c-'0';
			} while (Isdigit(c= nbp->snextc()));
			l= (neg == '-') ? -l : l;
		
			switch (code) {
			case eTShort:
				*((short*)vp)= (short)l;
				break;
			case eTUShort:
				*((u_short*)vp)= (u_short)l;
				break;
			case eTInt:
				*((int*)vp)= (int)l;
				break;
			case eTUInt:
				*((u_int*)vp)= (u_int)l;
				break;
			case eTLong:
				*((long*)vp)= (long)l;
				break;
			case eTULong:
				*((u_long*)vp)= (u_long)l;
				break;
			}
		} else
			state |= _fail;
		break;

	case eTFloat:
	case eTDouble:
		s= buf;
		switch (c) {
		case '-':
		case '+':
			*s++= c;
			c= nbp->snextc();
		}
		
		/* get integral part */
		while (Isdigit(c)) {
			*s++= c;
			c= nbp->snextc();
		}
		
		/* get fraction */
		if (c == '.') {
			do {
				*s++= c;
				c= nbp->snextc();
			} while (Isdigit(c));
		}
		
		/* get exponent */
		if (c == 'e' || c == 'E') {
			*s++= c;
			switch (c= nbp->snextc()) {
			case EOF:
				state|= _fail;
				return *this;
			case '-':
			case '+':
				*s++= c;
				c= nbp->snextc();
			}
			while (Isdigit(c)) {
				*s++= c;
				c= nbp->snextc();
			}
		}
		
		*s= 0;
		if (code == eTDouble)
			*((double*)vp)= atof(buf);
		else
			*((float*)vp)= (float) atof(buf);
		break;

	case eTString:
	case eTUString:
		s= (char*) vp;
		while (!Isspace(c) && c != EOF) {
			*s++= c;
			c= nbp->snextc();
		}
		*s= '\0';
		break;

	case eTStream:
		{
			register StreamBuf *b= ((Stream*)vp)->getsbuf();
			while (c != EOF) {
				b->sputc(c);
				//if (b->sputc(c) == EOF)
				//    break;
				c= nbp->snextc();
			}
		}
		break;
	default:
		fprintf(stderr, "IStream::Scan: unknown code %d\n", code);
		break;
	}
	return *this;
}

/*
IStream& IStream::get(char *buf, int len, char term)
{
	register int c;
	register StreamBuf *nbp= bp;

	eatwhite();

	if (state) {
		state |= _fail;
		return *this;
	}

	if ((c= bp->sgetc()) == EOF) {
		state |= _fail | _eof;
		return *this;
	}

	while (c != term && c != EOF && len > 1) {
		*buf++= c;
		c= nbp->snextc();
		len--;
	}
	*buf= '\0';
	if (c == EOF)
		state |= _eof;
	return *this;
}
*/

IStream& IStream::putback(register char c)
{
	bp->sputbackc(c);
	return *this;
}

IStream& IStream::get(u_char &b)
{
	flush();

	if (state) {
		state |= _fail;
		return *this;
	}
	register tc= bp->sgetc();
	if (tc == EOF)
		state |= _fail|_eof;
	else {
		b= (u_char) tc;
		bp->stossc();
	}
	return *this;
}

long IStream::GetBigEndian(int bytes)
{
	register long w= 0;
	register int tc;

	flush();

	if (state || bytes < 1 || bytes > 4) {
		state |= _fail;
		return 0;
	}
	
	while (bytes-- > 0) {
		if ((tc= bp->sgetc()) == EOF) {
			state |= _fail|_eof;
			return 0;
		} else {
			bp->stossc();
			w= w*256 + tc;
		}
	}
	return w;
}

char IStream::GetChar()
{
	flush();

	if (state) {
		state |= _fail;
		return 0;
	}
	register tc= bp->sgetc();
	if (tc == EOF) {
		state |= _fail|_eof;
	} else
		bp->stossc();
	return tc;
}

u_char IStream::GetByte()
{
	u_char b;
	flush();

	if (state) {
		state |= _fail;
		return 0;
	}
	register tc= bp->sgetc();
	if (tc == EOF) {
		state |= _fail|_eof;
	} else {
		b= tc;
		bp->stossc();
	}
	return b;
}

u_char IStream::GetHex()
{
	char c;
	u_long val= 0;

	for (int i= 0; i < 2; ) {
		get(c);
		if (Isxdigit(c)) {
			val= val*16 + HexValue(c);
			i++;
		}
	}
	return val;
}

IStream &IStream::ReadString(char **s, int *lp)
{
	register u_char *p, *pp;
	int l;
	u_char cc;
	char c;

	// the following hack is for compatibility with code that writes
	// strings without a leading string length and without quoting
	// special characters (like old Symbol::PrintOn)
	(*this) >> c;
	putback(c);
	if (!Isdigit(c) && c != '-') {
		char buf[1000];
		(*this) >> buf;
		if (s)
			*s= strsave(buf);
		if (lp)
			*lp= strlen(buf);
		return *this;
	}

	(*this) >> l;       // the length (includes end of string 0)

	if (l < 0) {        // a null ptr string is written as -1
		if (s)
			*s= 0;
		if (lp)
			*lp= 0;
	} else {
		(*this) >> cc;
		pp= p= new u_char[l];
		switch (cc) {
		case '[':       // binary string
			read(pp, l);
			break;
		case '"':       // binary string with quoted special chars
			Push(new StringDecoder);
			read(pp, l);
			Pop();
			break;
		case '<':       // hex string
			Push(new HexDecoder(">"));
			read(pp, l);
			Pop();
			break;
		default:
			fprintf(stderr, "IStream::ReadString: unexpected char\n");
			break;
		}
		if (s)
			*s= (char*)pp;
		else
			delete pp;
		if (lp)
			*lp= l;
	}
	return *this;
}

IStream &IStream::getline(char *buf, int len, char term)
{
	int i;
	char c;
	
	for (i= 0; get(c) && i < len-1;) {
		buf[i++]= c;
		if (c == term)
			break;
	}
	buf[i]= 0;
	return *this;
}

